home *** CD-ROM | disk | FTP | other *** search
/ Clickx 47 / Clickx 47.iso / assets / software / Miro_Installer.exe / Miro_Downloader.exe / dl_daemon / download.pyc (.txt) < prev   
Encoding:
Python Compiled Bytecode  |  2008-01-10  |  32.2 KB  |  1,130 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.5)
  3.  
  4. import os
  5. import sys
  6. import bsddb
  7. import shutil
  8. import types
  9. from gettext import gettext as _
  10. from os import remove
  11. import re
  12. from threading import RLock, Event, Thread
  13. import traceback
  14. from copy import copy
  15. import libtorrent as lt
  16. from clock import clock
  17. from download_utils import cleanFilename, nextFreeFilename, checkFilenameExtension, filterDirectoryName
  18. from download_utils import filenameFromURL, getFileURLPath
  19. import eventloop
  20. import httpclient
  21. import datetime
  22. import logging
  23. import fileutil
  24. import config
  25. import prefs
  26. from sha import sha
  27. from dl_daemon import command, daemon
  28. from datetime import timedelta
  29. from util import checkF, checkU, stringify
  30. import platformutils
  31. import string
  32. chatter = True
  33. _downloads = { }
  34. _lock = RLock()
  35.  
  36. def configReceived():
  37.     torrentSession.startup()
  38.  
  39.  
  40. def createDownloader(url, contentType, dlid):
  41.     checkU(url)
  42.     checkU(contentType)
  43.     if contentType == u'application/x-bittorrent':
  44.         return BTDownloader(url, dlid)
  45.     else:
  46.         return HTTPDownloader(url, dlid)
  47.  
  48.  
  49. def startNewDownload(url, dlid, contentType, channelName):
  50.     checkU(url)
  51.     checkU(contentType)
  52.     if channelName:
  53.         checkF(channelName)
  54.     
  55.     dl = createDownloader(url, contentType, dlid)
  56.     dl.channelName = channelName
  57.     _downloads[dlid] = dl
  58.  
  59.  
  60. def pauseDownload(dlid):
  61.     
  62.     try:
  63.         download = _downloads[dlid]
  64.     except:
  65.         return True
  66.  
  67.     return download.pause()
  68.  
  69.  
  70. def startDownload(dlid):
  71.     
  72.     try:
  73.         download = _downloads[dlid]
  74.     except KeyError:
  75.         err = u'in startDownload(): no downloader with id %s' % dlid
  76.         c = command.DownloaderErrorCommand(daemon.lastDaemon, err)
  77.         c.send()
  78.         return True
  79.  
  80.     return download.start()
  81.  
  82.  
  83. def stopDownload(dlid, delete):
  84.     
  85.     try:
  86.         _lock.acquire()
  87.         
  88.         try:
  89.             download = _downloads[dlid]
  90.             del _downloads[dlid]
  91.         finally:
  92.             _lock.release()
  93.  
  94.     except:
  95.         return True
  96.  
  97.     return download.stop(delete)
  98.  
  99.  
  100. def stopUpload(dlid):
  101.     
  102.     try:
  103.         _lock.acquire()
  104.         
  105.         try:
  106.             download = _downloads[dlid]
  107.             if download.state != u'uploading':
  108.                 return None
  109.             
  110.             del _downloads[dlid]
  111.         finally:
  112.             _lock.release()
  113.  
  114.     except:
  115.         return None
  116.  
  117.     return download.stopUpload()
  118.  
  119.  
  120. def migrateDownload(dlid, directory):
  121.     checkF(directory)
  122.     
  123.     try:
  124.         download = _downloads[dlid]
  125.     except:
  126.         pass
  127.  
  128.     if download.state in (u'finished', u'uploading'):
  129.         download.moveToDirectory(directory)
  130.     
  131.  
  132.  
  133. def getDownloadStatus(dlids = None):
  134.     statuses = { }
  135.     for key in _downloads.keys():
  136.         if dlids is None and dlids == key or key in dlids:
  137.             
  138.             try:
  139.                 statuses[key] = _downloads[key].getStatus()
  140.  
  141.             continue
  142.     
  143.     return statuses
  144.  
  145.  
  146. def shutDown():
  147.     logging.info('Shutting down downloaders...')
  148.     for dlid in _downloads:
  149.         _downloads[dlid].shutdown()
  150.     
  151.     torrentSession.shutdown()
  152.  
  153.  
  154. def restoreDownloader(downloader):
  155.     downloader = copy(downloader)
  156.     dlerType = downloader.get('dlerType')
  157.     if dlerType == u'HTTP':
  158.         dl = HTTPDownloader(restore = downloader)
  159.     elif dlerType == u'BitTorrent':
  160.         dl = BTDownloader(restore = downloader)
  161.     else:
  162.         err = u'in restoreDownloader(): unknown dlerType: %s' % dlerType
  163.         c = command.DownloaderErrorCommand(daemon.lastDaemon, err)
  164.         c.send()
  165.         return None
  166.     _downloads[downloader['dlid']] = dl
  167.  
  168.  
  169. class TorrentSession:
  170.     '''Contains the bittorrent session and handles updating all running bittorrents'''
  171.     
  172.     def __init__(self):
  173.         self.torrents = set()
  174.         self.session = None
  175.         self.pnp_on = None
  176.         self.pe_set = None
  177.         self.enc_req = None
  178.  
  179.     
  180.     def startup(self):
  181.         fingerprint = lt.fingerprint('MR', 1, 1, 0, 0)
  182.         self.session = lt.session(fingerprint)
  183.         self.listen()
  184.         self.setUpnp()
  185.         self.setUploadLimit()
  186.         self.setDownloadLimit()
  187.         self.setEncryption()
  188.         config.addChangeCallback(self.configChanged)
  189.  
  190.     
  191.     def listen(self):
  192.         self.session.listen_on(config.get(prefs.BT_MIN_PORT), config.get(prefs.BT_MAX_PORT))
  193.  
  194.     
  195.     def setUpnp(self):
  196.         useUpnp = config.get(prefs.USE_UPNP)
  197.         if useUpnp != self.pnp_on:
  198.             self.pnp_on = useUpnp
  199.             if useUpnp:
  200.                 self.session.start_upnp()
  201.             else:
  202.                 self.session.stop_upnp()
  203.         
  204.  
  205.     
  206.     def setUploadLimit(self):
  207.         limit = -1
  208.         if config.get(prefs.LIMIT_UPSTREAM):
  209.             limit = config.get(prefs.UPSTREAM_LIMIT_IN_KBS) * 1024
  210.         
  211.         self.session.set_upload_rate_limit(limit)
  212.  
  213.     
  214.     def setDownloadLimit(self):
  215.         limit = -1
  216.         if config.get(prefs.LIMIT_DOWNSTREAM_BT):
  217.             limit = config.get(prefs.DOWNSTREAM_BT_LIMIT_IN_KBS) * 1024
  218.         
  219.         self.session.set_download_rate_limit(limit)
  220.  
  221.     
  222.     def setEncryption(self):
  223.         if self.pe_set is None:
  224.             self.pe_set = lt.pe_settings()
  225.         
  226.         enc_req = config.get(prefs.BT_ENC_REQ)
  227.         if enc_req != self.enc_req:
  228.             self.enc_req = enc_req
  229.             if enc_req:
  230.                 self.pe_set.in_enc_policy = lt.enc_policy.forced
  231.                 self.pe_set.out_enc_policy = lt.enc_policy.forced
  232.             else:
  233.                 self.pe_set.in_enc_policy = lt.enc_policy.enabled
  234.                 self.pe_set.out_enc_policy = lt.enc_policy.enabled
  235.             self.session.set_pe_settings(self.pe_set)
  236.         
  237.  
  238.     
  239.     def shutdown(self):
  240.         config.removeChangeCallback(self.configChanged)
  241.         del self.session
  242.  
  243.     
  244.     def configChanged(self, key, value):
  245.         if key == prefs.BT_MIN_PORT.key:
  246.             if value > self.session.listen_port():
  247.                 self.listen()
  248.             
  249.         
  250.         if key == prefs.BT_MAX_PORT.key:
  251.             if value < self.session.listen_port():
  252.                 self.listen()
  253.             
  254.         
  255.         if key == prefs.USE_UPNP.key:
  256.             self.setUpnp()
  257.         
  258.         if key in (prefs.LIMIT_UPSTREAM.key, prefs.UPSTREAM_LIMIT_IN_KBS.key):
  259.             self.setUploadLimit()
  260.         
  261.         if key in (prefs.LIMIT_DOWNSTREAM_BT.key, prefs.DOWNSTREAM_BT_LIMIT_IN_KBS.key):
  262.             self.setDownloadLimit()
  263.         
  264.         if key == prefs.BT_ENC_REQ.key:
  265.             self.setEncryption()
  266.         
  267.  
  268.     
  269.     def addTorrent(self, torrent):
  270.         self.torrents.add(torrent)
  271.  
  272.     
  273.     def removeTorrent(self, torrent):
  274.         
  275.         try:
  276.             self.torrents.remove(torrent)
  277.         except:
  278.             pass
  279.  
  280.  
  281.     
  282.     def updateTorrents(self):
  283.         for x in self.torrents:
  284.             pass
  285.         
  286.  
  287.  
  288. torrentSession = TorrentSession()
  289.  
  290. class DownloadStatusUpdater:
  291.     """Handles updating status for all in progress downloaders.
  292.  
  293.     On OS X and gtk if the user is on the downloads page and has a bunch of
  294.     downloads going, this can be a fairly CPU intensive task.
  295.     DownloadStatusUpdaters mitigate this in 2 ways.
  296.  
  297.     1) DownloadStatusUpdater objects batch all status updates into one big
  298.     update which takes much less CPU.  
  299.     
  300.     2) The update don't happen fairly infrequently (currently every 5 seconds).
  301.     
  302.     Becouse updates happen infrequently, DownloadStatusUpdaters should only be
  303.     used for progress updates, not events like downloads starting/finishing.
  304.     For those just call updateClient() since they are more urgent, and don't
  305.     happen often enough to cause CPU problems.
  306.     """
  307.     UPDATE_CLIENT_INTERVAL = 5
  308.     
  309.     def __init__(self):
  310.         self.toUpdate = set()
  311.  
  312.     
  313.     def startUpdates(self):
  314.         eventloop.addTimeout(self.UPDATE_CLIENT_INTERVAL, self.doUpdate, 'Download status update')
  315.  
  316.     
  317.     def doUpdate(self):
  318.         
  319.         try:
  320.             torrentSession.updateTorrents()
  321.             statuses = []
  322.             for downloader in self.toUpdate:
  323.                 statuses.append(downloader.getStatus())
  324.             
  325.             self.toUpdate = set()
  326.             if statuses:
  327.                 command.BatchUpdateDownloadStatus(daemon.lastDaemon, statuses).send()
  328.         finally:
  329.             eventloop.addTimeout(self.UPDATE_CLIENT_INTERVAL, self.doUpdate, 'Download status update')
  330.  
  331.  
  332.     
  333.     def queueUpdate(self, downloader):
  334.         self.toUpdate.add(downloader)
  335.  
  336.  
  337. downloadUpdater = DownloadStatusUpdater()
  338. RETRY_TIMES = (60, 5 * 60, 10 * 60, 30 * 60, 60 * 60, 2 * 60 * 60, 6 * 60 * 60, 24 * 60 * 60)
  339.  
  340. class BGDownloader:
  341.     
  342.     def __init__(self, url, dlid):
  343.         self.dlid = dlid
  344.         self.url = url
  345.         self.startTime = clock()
  346.         self.endTime = self.startTime
  347.         self.shortFilename = filenameFromURL(url)
  348.         self.pickInitialFilename()
  349.         self.state = u'downloading'
  350.         self.currentSize = 0
  351.         self.totalSize = -1
  352.         self.blockTimes = []
  353.         self.shortReasonFailed = self.reasonFailed = u'No Error'
  354.         self.retryTime = None
  355.         self.retryCount = -1
  356.  
  357.     
  358.     def getURL(self):
  359.         return self.url
  360.  
  361.     
  362.     def getStatus(self):
  363.         return {
  364.             'dlid': self.dlid,
  365.             'url': self.url,
  366.             'state': self.state,
  367.             'totalSize': self.totalSize,
  368.             'currentSize': self.currentSize,
  369.             'eta': self.getETA(),
  370.             'rate': self.getRate(),
  371.             'uploaded': 0,
  372.             'filename': self.filename,
  373.             'startTime': self.startTime,
  374.             'endTime': self.endTime,
  375.             'shortFilename': self.shortFilename,
  376.             'reasonFailed': self.reasonFailed,
  377.             'shortReasonFailed': self.shortReasonFailed,
  378.             'dlerType': None,
  379.             'retryTime': self.retryTime,
  380.             'retryCount': self.retryCount,
  381.             'channelName': self.channelName }
  382.  
  383.     
  384.     def updateClient(self):
  385.         x = command.UpdateDownloadStatus(daemon.lastDaemon, self.getStatus())
  386.         return x.send()
  387.  
  388.     
  389.     def pickInitialFilename(self):
  390.         '''Pick a path to download to based on self.shortFilename.
  391.  
  392.         This method sets self.filename, as well as creates any leading paths
  393.         needed to start downloading there.
  394.         '''
  395.         downloadDir = os.path.join(config.get(prefs.MOVIES_DIRECTORY), 'Incomplete Downloads')
  396.         
  397.         try:
  398.             os.makedirs(downloadDir)
  399.         except:
  400.             pass
  401.  
  402.         cleaned = cleanFilename(self.shortFilename + '.part')
  403.         self.filename = nextFreeFilename(os.path.join(downloadDir, cleaned))
  404.  
  405.     
  406.     def moveToMoviesDirectory(self):
  407.         '''Move our downloaded file from the Incomplete Downloads directoy to
  408.         the movies directory.
  409.         '''
  410.         if chatter:
  411.             logging.info('moving to movies directory filename is %s', self.filename)
  412.         
  413.         self.moveToDirectory(config.get(prefs.MOVIES_DIRECTORY))
  414.  
  415.     
  416.     def moveToDirectory(self, directory):
  417.         checkF(directory)
  418.         if self.channelName:
  419.             channelName = filterDirectoryName(self.channelName)
  420.             directory = os.path.join(directory, channelName)
  421.             
  422.             try:
  423.                 os.makedirs(directory)
  424.  
  425.         
  426.         newfilename = os.path.join(directory, self.shortFilename)
  427.         if newfilename == self.filename:
  428.             return None
  429.         
  430.         newfilename = nextFreeFilename(newfilename)
  431.         
  432.         def callback():
  433.             self.filename = newfilename
  434.             self.updateClient()
  435.  
  436.         fileutil.migrate_file(self.filename, newfilename, callback)
  437.  
  438.     
  439.     def getETA(self):
  440.         if self.totalSize == -1:
  441.             return -1
  442.         
  443.         rate = self.getRate()
  444.         if rate > 0:
  445.             return (self.totalSize - self.currentSize) / rate
  446.         else:
  447.             return 0
  448.  
  449.     
  450.     def getRate(self):
  451.         now = clock()
  452.         if self.endTime != self.startTime:
  453.             rate = self.currentSize / (self.endTime - self.startTime)
  454.         else:
  455.             haltedSince = now
  456.             for time, size in reversed(self.blockTimes):
  457.                 if size == self.currentSize:
  458.                     haltedSince = time
  459.                     continue
  460.             
  461.             if now - haltedSince > self.HALTED_THRESHOLD:
  462.                 rate = 0
  463.             else:
  464.                 
  465.                 try:
  466.                     timespan = now - self.blockTimes[0][0]
  467.                     if timespan != 0:
  468.                         endSize = self.blockTimes[-1][1]
  469.                         startSize = self.blockTimes[0][1]
  470.                         rate = (endSize - startSize) / timespan
  471.                     else:
  472.                         rate = 0
  473.                 except IndexError:
  474.                     rate = 0
  475.  
  476.         return rate
  477.  
  478.     
  479.     def retryDownload(self):
  480.         self.retryDC = None
  481.         self.start()
  482.  
  483.     
  484.     def handleTemporaryError(self, shortReason, reason):
  485.         self.state = u'offline'
  486.         self.reasonFailed = reason
  487.         self.shortReasonFailed = shortReason
  488.         self.retryCount = self.retryCount + 1
  489.         if self.retryCount >= len(RETRY_TIMES):
  490.             self.retryCount = len(RETRY_TIMES) - 1
  491.         
  492.         self.retryDC = eventloop.addTimeout(RETRY_TIMES[self.retryCount], self.retryDownload, 'Logarithmic retry')
  493.         self.retryTime = datetime.datetime.now() + datetime.timedelta(seconds = RETRY_TIMES[self.retryCount])
  494.         self.updateClient()
  495.  
  496.     
  497.     def handleError(self, shortReason, reason):
  498.         self.state = u'failed'
  499.         self.reasonFailed = reason
  500.         self.shortReasonFailed = shortReason
  501.         self.updateClient()
  502.  
  503.     
  504.     def handleNetworkError(self, error):
  505.         if isinstance(error, httpclient.NetworkError):
  506.             if isinstance(error, httpclient.MalformedURL) or isinstance(error, httpclient.UnexpectedStatusCode):
  507.                 self.handleError(error.getFriendlyDescription(), error.getLongDescription())
  508.             else:
  509.                 self.handleTemporaryError(error.getFriendlyDescription(), error.getLongDescription())
  510.         else:
  511.             logging.info('WARNING: grabURL errback not called with NetworkError')
  512.             self.handleError(str(error), str(error))
  513.  
  514.     
  515.     def handleGenericError(self, longDescription):
  516.         self.handleError(_('Error'), longDescription)
  517.  
  518.     
  519.     def acceptDownloadSize(self, size):
  520.         accept = True
  521.         if config.get(prefs.PRESERVE_DISK_SPACE):
  522.             if size < 0:
  523.                 size = 0
  524.             
  525.             preserved = config.get(prefs.PRESERVE_X_GB_FREE) * 1024 * 1024 * 1024
  526.             available = platformutils.getAvailableBytesForMovies() - preserved
  527.             accept = size <= available
  528.         
  529.         return accept
  530.  
  531.  
  532.  
  533. class HTTPDownloader(BGDownloader):
  534.     UPDATE_CLIENT_WINDOW = 12
  535.     HALTED_THRESHOLD = 3
  536.     
  537.     def __init__(self, url = None, dlid = None, restore = None):
  538.         self.retryDC = None
  539.         self.channelName = None
  540.         if restore is not None:
  541.             if not isinstance(restore.get('totalSize', 0), int):
  542.                 restore = None
  543.             
  544.         
  545.         if restore is not None:
  546.             self.__dict__.update(restore)
  547.             self.blockTimes = []
  548.             self.restartOnError = True
  549.         else:
  550.             BGDownloader.__init__(self, url, dlid)
  551.             self.restartOnError = False
  552.         self.client = None
  553.         self.filehandle = None
  554.         if self.state == 'downloading':
  555.             self.startDownload()
  556.         elif self.state == 'offline':
  557.             self.start()
  558.         else:
  559.             self.updateClient()
  560.  
  561.     
  562.     def resetBlockTimes(self):
  563.         self.blockTimes = [
  564.             (clock(), self.currentSize)]
  565.  
  566.     
  567.     def startNewDownload(self):
  568.         self.currentSize = 0
  569.         self.totalSize = -1
  570.         self.startDownload()
  571.  
  572.     
  573.     def startDownload(self):
  574.         if self.retryDC:
  575.             self.retryDC.cancel()
  576.             self.retryDC = None
  577.         
  578.         if self.currentSize == 0:
  579.             headerCallback = self.onHeaders
  580.         else:
  581.             headerCallback = self.onHeadersRestart
  582.         self.client = httpclient.grabURL(self.url, self.onDownloadFinished, self.onDownloadError, headerCallback, self.onBodyData, start = self.currentSize)
  583.         self.resetBlockTimes()
  584.         self.updateClient()
  585.  
  586.     
  587.     def cancelRequest(self):
  588.         if self.client is not None:
  589.             self.client.cancel()
  590.             self.client = None
  591.         
  592.  
  593.     
  594.     def handleError(self, shortReason, reason):
  595.         BGDownloader.handleError(self, shortReason, reason)
  596.         self.cancelRequest()
  597.         
  598.         try:
  599.             remove(self.filename)
  600.         except:
  601.             pass
  602.  
  603.         self.currentSize = 0
  604.         self.totalSize = -1
  605.  
  606.     
  607.     def handleTemporaryError(self, shortReason, reason):
  608.         BGDownloader.handleTemporaryError(self, shortReason, reason)
  609.         self.cancelRequest()
  610.  
  611.     
  612.     def handleWriteError(self, error):
  613.         self.handleGenericError(_('Could not write to %s') % stringify(self.filename))
  614.         if self.filehandle is not None:
  615.             
  616.             try:
  617.                 self.filehandle.close()
  618.  
  619.         
  620.         
  621.         try:
  622.             remove(self.filename)
  623.         except:
  624.             pass
  625.  
  626.  
  627.     
  628.     def onHeaders(self, info):
  629.         if info['contentLength'] != None:
  630.             self.totalSize = info['contentLength']
  631.         
  632.         if self.client.gotBadStatusCode:
  633.             error = httpclient.UnexpectedStatusCode(info['status'])
  634.             self.handleNetworkError(error)
  635.             return None
  636.         
  637.         if not self.acceptDownloadSize(self.totalSize):
  638.             self.handleError(_('Not enough disk space'), _('%s MB required to store this video') % self.totalSize / 1048576)
  639.             return None
  640.         
  641.         self.retryCount = -1
  642.         self.shortFilename = cleanFilename(info['filename'])
  643.         self.shortFilename = checkFilenameExtension(self.shortFilename, info)
  644.         self.pickInitialFilename()
  645.         
  646.         try:
  647.             self.filehandle = file(self.filename, 'w+b')
  648.         except IOError:
  649.             self.handleGenericError("Couldn't open %s for writing" % stringify(self.filename))
  650.             return None
  651.  
  652.         if self.totalSize > 0:
  653.             
  654.             try:
  655.                 self.filehandle.seek(self.totalSize - 1)
  656.                 self.filehandle.write(' ')
  657.                 self.filehandle.seek(0)
  658.             except IOError:
  659.                 error = None
  660.                 self.handleWriteError(error)
  661.                 return None
  662.             except:
  663.                 None<EXCEPTION MATCH>IOError
  664.             
  665.  
  666.         None<EXCEPTION MATCH>IOError
  667.         self.updateClient()
  668.  
  669.     
  670.     def onHeadersRestart(self, info):
  671.         self.restartOnError = False
  672.         if info['status'] != 206 or 'content-range' not in info:
  673.             self.currentSize = 0
  674.             self.totalSize = -1
  675.             self.resetBlockTimes()
  676.             if not self.client.gotBadStatusCode:
  677.                 self.onHeaders(info)
  678.             else:
  679.                 self.cancelRequest()
  680.                 self.startNewDownload()
  681.             return None
  682.         
  683.         
  684.         try:
  685.             self.parseContentRange(info['content-range'])
  686.         except ValueError:
  687.             logging.info('WARNING, bad content-range: %r', info['content-range'])
  688.             logging.info('currentSize: %d totalSize: %d', self.currentSize, self.totalSize)
  689.             self.cancelRequest()
  690.             self.startNewDownload()
  691.  
  692.         
  693.         try:
  694.             self.filehandle = file(self.filename, 'r+b')
  695.             self.filehandle.seek(self.currentSize)
  696.         except IOError:
  697.             e = None
  698.             self.handleWriteError(e)
  699.  
  700.         self.retryCount = -1
  701.         self.updateClient()
  702.  
  703.     
  704.     def parseContentRange(self, contentRange):
  705.         """Parse the content-range header from an http response.  If it's
  706.         badly formatted, or it's not what we were expecting based on the state
  707.         we restored to, raise a ValueError.
  708.         """
  709.         m = re.search('bytes\\s+(\\d+)-(\\d+)/(\\d+)', contentRange)
  710.         if m is None:
  711.             raise ValueError()
  712.         
  713.         start = int(m.group(1))
  714.         end = int(m.group(2))
  715.         totalSize = int(m.group(3))
  716.         if start > self.currentSize or end + 1 != totalSize:
  717.             raise ValueError()
  718.         
  719.         self.currentSize = start
  720.         self.totalSize = totalSize
  721.  
  722.     
  723.     def onDownloadError(self, error):
  724.         if self.restartOnError:
  725.             self.restartOnError = False
  726.             self.startDownload()
  727.         else:
  728.             self.client = None
  729.             self.handleNetworkError(error)
  730.  
  731.     
  732.     def onBodyData(self, data):
  733.         if self.state != 'downloading':
  734.             return None
  735.         
  736.         self.updateRateAndETA(len(data))
  737.         downloadUpdater.queueUpdate(self)
  738.         
  739.         try:
  740.             self.filehandle.write(data)
  741.         except IOError:
  742.             e = None
  743.             self.handleWriteError(e)
  744.  
  745.  
  746.     
  747.     def onDownloadFinished(self, response):
  748.         self.client = None
  749.         
  750.         try:
  751.             self.filehandle.close()
  752.         except Exception:
  753.             e = None
  754.             self.handleWriteError(e)
  755.             return None
  756.  
  757.         self.state = 'finished'
  758.         if self.totalSize == -1:
  759.             self.totalSize = self.currentSize
  760.         
  761.         self.endTime = clock()
  762.         
  763.         try:
  764.             self.moveToMoviesDirectory()
  765.         except IOError:
  766.             e = None
  767.             self.handleWriteError(e)
  768.  
  769.         self.resetBlockTimes()
  770.         self.updateClient()
  771.  
  772.     
  773.     def getStatus(self):
  774.         data = BGDownloader.getStatus(self)
  775.         data['dlerType'] = 'HTTP'
  776.         return data
  777.  
  778.     
  779.     def updateRateAndETA(self, length):
  780.         now = clock()
  781.         self.currentSize = self.currentSize + length
  782.         self.blockTimes.append((now, self.currentSize))
  783.         window_start = now - self.UPDATE_CLIENT_WINDOW
  784.         i = 0
  785.         for i in xrange(len(self.blockTimes)):
  786.             if self.blockTimes[0][0] >= window_start:
  787.                 break
  788.                 continue
  789.         
  790.         self.blockTimes = self.blockTimes[i:]
  791.  
  792.     
  793.     def pause(self):
  794.         if self.state != 'stopped':
  795.             self.cancelRequest()
  796.             self.state = 'paused'
  797.             self.updateClient()
  798.         
  799.  
  800.     
  801.     def stop(self, delete):
  802.         if self.state == 'downloading':
  803.             if self.filehandle is not None:
  804.                 
  805.                 try:
  806.                     if not self.filehandle.closed:
  807.                         self.filehandle.close()
  808.                     
  809.                     remove(self.filename)
  810.  
  811.             
  812.         
  813.         if delete:
  814.             
  815.             try:
  816.                 if os.path.isdir(self.filename):
  817.                     shutil.rmtree(self.filename)
  818.                 else:
  819.                     remove(self.filename)
  820.  
  821.         
  822.         self.currentSize = 0
  823.         self.cancelRequest()
  824.         self.state = 'stopped'
  825.         self.updateClient()
  826.  
  827.     
  828.     def stopUpload(self):
  829.         pass
  830.  
  831.     
  832.     def start(self):
  833.         if self.state in ('paused', 'stopped', 'offline'):
  834.             self.state = 'downloading'
  835.             self.startDownload()
  836.         
  837.  
  838.     
  839.     def shutdown(self):
  840.         self.cancelRequest()
  841.         self.updateClient()
  842.  
  843.  
  844.  
  845. class BTDownloader(BGDownloader):
  846.     
  847.     def __init__(self, url = None, item = None, restore = None):
  848.         self.metainfo = None
  849.         self.torrent = None
  850.         self.rate = self.eta = 0
  851.         self.upRate = self.uploaded = 0
  852.         self.activity = None
  853.         self.fastResumeData = None
  854.         self.retryDC = None
  855.         self.channelName = None
  856.         self.uploadedStart = 0
  857.         self.restarting = False
  858.         self.seeders = -1
  859.         self.leechers = -1
  860.         if restore is not None:
  861.             self.firstTime = False
  862.             self.restoreState(restore)
  863.         else:
  864.             self.firstTime = True
  865.             BGDownloader.__init__(self, url, item)
  866.             self.runDownloader()
  867.  
  868.     
  869.     def _startTorrent(self):
  870.         
  871.         try:
  872.             torrent_info = lt.torrent_info(lt.bdecode(self.metainfo))
  873.             self.totalSize = torrent_info.total_size()
  874.             if self.firstTime and not self.acceptDownloadSize(self.totalSize):
  875.                 self.handleError(_('Not enough disk space'), _('%s MB required to store this video') % self.totalSize / 1048576)
  876.                 return None
  877.             
  878.             name = stringify(self.filename)
  879.             if self.fastResumeData:
  880.                 resume = lt.bdecode(self.fastResumeData)
  881.                 self.torrent = torrentSession.session.add_torrent(torrent_info, name, lt.bdecode(self.fastResumeData), lt.storage_mode_t.storage_mode_allocate)
  882.             else:
  883.                 self.torrent = torrentSession.session.add_torrent(torrent_info, name, None, lt.storage_mode_t.storage_mode_allocate)
  884.         except:
  885.             self.handleError(_('BitTorrent failure'), _('BitTorrent failed to startup'))
  886.  
  887.         torrentSession.addTorrent(self)
  888.  
  889.     
  890.     def _shutdownTorrent(self):
  891.         
  892.         try:
  893.             torrentSession.removeTorrent(self)
  894.             if self.torrent is not None:
  895.                 self.torrent.pause()
  896.                 self.fastResumeData = lt.bencode(self.torrent.write_resume_data())
  897.                 torrentSession.session.remove_torrent(self.torrent, 0)
  898.                 self.torrent = None
  899.         except:
  900.             logging.exception('DTV: Warning: Error shutting down torrent')
  901.  
  902.  
  903.     
  904.     def _pauseTorrent(self):
  905.         
  906.         try:
  907.             torrentSession.removeTorrent(self)
  908.             if self.torrent is not None:
  909.                 self.torrent.pause()
  910.         except:
  911.             logging.exception('DTV: Warning: Error pausing torrent')
  912.  
  913.  
  914.     
  915.     def _resumeTorrent(self):
  916.         if self.torrent is not None:
  917.             
  918.             try:
  919.                 self.torrent.resume()
  920.                 torrentSession.addTorrent(self)
  921.             logging.exception('DTV: Warning: Error resuming torrent')
  922.  
  923.         else:
  924.             self._startTorrent()
  925.  
  926.     
  927.     def updateStatus(self):
  928.         """
  929.         activity -- string specifying what's currently happening or None for
  930.                 normal operations.  
  931.         upRate -- upload rate in B/s
  932.         downRate -- download rate in B/s
  933.         upTotal -- total MB uploaded
  934.         downTotal -- total MB downloaded
  935.         fractionDone -- what portion of the download is completed.
  936.         timeEst -- estimated completion time, in seconds.
  937.         totalSize -- total size of the torrent in bytes
  938.         """
  939.         status = self.torrent.status()
  940.         self.totalSize = status.total_wanted
  941.         self.rate = status.download_payload_rate
  942.         self.upRate = status.upload_payload_rate
  943.         self.uploaded = status.total_payload_upload + self.uploadedStart
  944.         self.seeders = status.num_complete
  945.         self.leechers = status.num_incomplete
  946.         
  947.         try:
  948.             self.eta = (status.total_wanted - status.total_wanted_done) / float(status.download_payload_rate)
  949.         except ZeroDivisionError:
  950.             self.eta = 0
  951.  
  952.         if status.state == lt.torrent_status.states.queued_for_checking:
  953.             self.activity = 'waiting to check existing files'
  954.         elif status.state == lt.torrent_status.states.checking_files:
  955.             self.activity = 'checking existing files'
  956.         elif status.state == lt.torrent_status.states.connecting_to_tracker:
  957.             self.activity = 'connecting to peers'
  958.         elif status.state == lt.torrent_status.states.allocating:
  959.             self.activity = 'allocating disk space'
  960.         else:
  961.             self.activity = None
  962.         self.currentSize = status.total_wanted_done
  963.         if self.state == 'downloading' and status.state == lt.torrent_status.states.seeding:
  964.             self.moveToMoviesDirectory()
  965.             self.state = 'uploading'
  966.             self.endTime = clock()
  967.             self.updateClient()
  968.         else:
  969.             downloadUpdater.queueUpdate(self)
  970.  
  971.     
  972.     def handleError(self, shortReason, reason):
  973.         self._shutdownTorrent()
  974.         BGDownloader.handleError(self, shortReason, reason)
  975.  
  976.     
  977.     def handleTemporaryError(self, shortReason, reason):
  978.         self._shutdownTorrent()
  979.         BGDownloader.handleTemporaryError(self, shortReason, reason)
  980.  
  981.     
  982.     def moveToDirectory(self, directory):
  983.         if self.state in ('uploading', 'downloading'):
  984.             self._shutdownTorrent()
  985.             BGDownloader.moveToDirectory(self, directory)
  986.             self._resumeTorrent()
  987.         else:
  988.             BGDownloader.moveToDirectory(self, directory)
  989.  
  990.     
  991.     def restoreState(self, data):
  992.         self.__dict__.update(data)
  993.         self.rate = self.eta = 0
  994.         self.upRate = 0
  995.         self.uploadedStart = self.uploaded
  996.         if self.state in ('downloading', 'uploading'):
  997.             self.runDownloader(done = True)
  998.         elif self.state == 'offline':
  999.             self.start()
  1000.         
  1001.  
  1002.     
  1003.     def getStatus(self):
  1004.         data = BGDownloader.getStatus(self)
  1005.         data['upRate'] = self.upRate
  1006.         data['uploaded'] = self.uploaded
  1007.         data['metainfo'] = self.metainfo
  1008.         data['fastResumeData'] = self.fastResumeData
  1009.         data['activity'] = self.activity
  1010.         data['dlerType'] = 'BitTorrent'
  1011.         data['seeders'] = self.seeders
  1012.         data['leechers'] = self.leechers
  1013.         return data
  1014.  
  1015.     
  1016.     def getRate(self):
  1017.         return self.rate
  1018.  
  1019.     
  1020.     def getETA(self):
  1021.         return self.eta
  1022.  
  1023.     
  1024.     def pause(self):
  1025.         self.state = 'paused'
  1026.         self._pauseTorrent()
  1027.         self.updateClient()
  1028.  
  1029.     
  1030.     def stop(self, delete):
  1031.         self.state = 'stopped'
  1032.         self._shutdownTorrent()
  1033.         self.updateClient()
  1034.         if delete:
  1035.             
  1036.             try:
  1037.                 if os.path.isdir(self.filename):
  1038.                     shutil.rmtree(self.filename)
  1039.                 else:
  1040.                     remove(self.filename)
  1041.  
  1042.         
  1043.  
  1044.     
  1045.     def stopUpload(self):
  1046.         self.state = 'finished'
  1047.         self._shutdownTorrent()
  1048.         self.updateClient()
  1049.  
  1050.     
  1051.     def start(self):
  1052.         if self.state not in ('paused', 'stopped', 'offline'):
  1053.             return None
  1054.         
  1055.         self.state = 'downloading'
  1056.         if self.retryDC:
  1057.             self.retryDC.cancel()
  1058.             self.retryDC = None
  1059.         
  1060.         self.updateClient()
  1061.         self.getMetainfo()
  1062.  
  1063.     
  1064.     def shutdown(self):
  1065.         self._shutdownTorrent()
  1066.         self.updateClient()
  1067.  
  1068.     
  1069.     def gotMetainfo(self):
  1070.         if not self.restarting:
  1071.             
  1072.             try:
  1073.                 metainfo = lt.bdecode(self.metainfo)
  1074.                 name = metainfo['info']['name']
  1075.             except RuntimeError:
  1076.                 self.handleError(_('Corrupt Torrent'), _('The torrent file at %s was not valid') % stringify(self.url))
  1077.                 return None
  1078.  
  1079.             name = name.decode('utf-8', 'replace')
  1080.             self.shortFilename = cleanFilename(name)
  1081.             self.pickInitialFilename()
  1082.         
  1083.         self.updateClient()
  1084.         self._resumeTorrent()
  1085.  
  1086.     
  1087.     def handleMetainfo(self, metainfo):
  1088.         self.metainfo = metainfo
  1089.         self.gotMetainfo()
  1090.  
  1091.     
  1092.     def onDescriptionDownload(self, info):
  1093.         self.handleMetainfo(info['body'])
  1094.  
  1095.     
  1096.     def onDescriptionDownloadFailed(self, exception):
  1097.         self.handleNetworkError(exception)
  1098.  
  1099.     
  1100.     def getMetainfo(self):
  1101.         if self.metainfo is None:
  1102.             if self.url.startswith('file://'):
  1103.                 path = getFileURLPath(self.url)
  1104.                 
  1105.                 try:
  1106.                     metainfoFile = open(path, 'rb')
  1107.                 except IOError:
  1108.                     self.handleError(_('Torrent file deleted'), _('The torrent file for this item was deleted outside of Miro.'))
  1109.                     return None
  1110.  
  1111.                 
  1112.                 try:
  1113.                     metainfo = metainfoFile.read()
  1114.                 finally:
  1115.                     metainfoFile.close()
  1116.  
  1117.                 self.handleMetainfo(metainfo)
  1118.             else:
  1119.                 httpclient.grabURL(self.getURL(), self.onDescriptionDownload, self.onDescriptionDownloadFailed)
  1120.         else:
  1121.             self.gotMetainfo()
  1122.  
  1123.     
  1124.     def runDownloader(self, done = False):
  1125.         self.restarting = done
  1126.         self.updateClient()
  1127.         self.getMetainfo()
  1128.  
  1129.  
  1130.